Expand description
In the context of game development, an “asset” is a piece of content that is loaded from disk and displayed in the game. Typically, these are authored by artists and designers (in contrast to code), are relatively large in size, and include everything from textures and models to sounds and music to levels and scripts.
This presents two main challenges:
- Assets take up a lot of memory; simply storing a copy for each instance of an asset in the game would be prohibitively expensive.
- Loading assets from disk is slow, and can cause long load times and delays.
These problems play into each other, for if assets are expensive to store in memory, then larger game worlds will need to load them from disk as needed, ideally without a loading screen.
As is common in Rust, non-blocking asset loading is done using async
, with background tasks used to load assets while the game is running.
Bevy coordinates these tasks using the AssetServer
resource, storing each loaded asset in a strongly-typed Assets<T>
collection (also a resource).
Handle
s serve as an id-based reference to entries in the Assets
collection, allowing them to be cheaply shared between systems,
and providing a way to initialize objects (generally entities) before the required assets are loaded.
In short: Handle
s are not the assets themselves, they just tell how to look them up!
§Loading assets
The AssetServer
is the main entry point for loading assets.
Typically, you’ll use the AssetServer::load
method to load an asset from disk, which returns a Handle
.
Note that this method does not attempt to reload the asset if it has already been loaded: as long as at least one handle has not been dropped,
calling AssetServer::load
on the same path will return the same handle.
The handle that’s returned can be used to instantiate various Component
s that require asset data to function,
which will then be spawned into the world as part of an entity.
To avoid assets “popping” into existence, you may want to check that all of the required assets are loaded before transitioning to a new scene.
This can be done by checking the LoadState
of the asset handle using AssetServer::is_loaded_with_dependencies
,
which will be true
when the asset is ready to use.
Keep track of what you’re waiting on by using a HashSet
of asset handles or similar data structure,
which iterate over and poll in your update loop, and transition to the new scene once all assets are loaded.
Bevy’s built-in states system can be very helpful for this!
§Modifying entities that use assets
If we later want to change the asset data a given component uses (such as changing an entity’s material), we have three options:
- Change the handle stored on the responsible component to the handle of a different asset
- Despawn the entity and spawn a new one with the new asset data.
- Use the
Assets
collection to directly modify the current handle’s asset data
The first option is the most common: just query for the component that holds the handle, and mutate it, pointing to the new asset. Check how the handle was passed in to the entity when it was spawned: if a mesh-related component required a handle to a mesh asset, you’ll need to find that component via a query and change the handle to the new mesh asset. This is so commonly done that you should think about strategies for how to store and swap handles in your game.
The second option is the simplest, but can be slow if done frequently, and can lead to frustrating bugs as references to the old entity (such as what is targeting it) and other data on the entity are lost. Generally, this isn’t a great strategy.
The third option has different semantics: rather than modifying the asset data for a single entity, it modifies the asset data for all entities using this handle. While this might be what you want, it generally isn’t!
§Hot reloading assets
Bevy supports asset hot reloading, allowing you to change assets on disk and see the changes reflected in your game without restarting.
When enabled, any changes to the underlying asset file will be detected by the AssetServer
, which will then reload the asset,
mutating the asset data in the Assets
collection and thus updating all entities that use the asset.
While it has limited uses in published games, it is very useful when developing, as it allows you to iterate quickly.
To enable asset hot reloading on desktop platforms, enable bevy
’s file_watcher
cargo feature.
To toggle it at runtime, you can use the watch_for_changes_override
field in the AssetPlugin
to enable or disable hot reloading.
§Procedural asset creation
Not all assets are loaded from disk: some are generated at runtime, such as procedural materials, sounds or even levels.
After creating an item of a type that implements Asset
, you can add it to the Assets
collection using Assets::add
.
Once in the asset collection, this data can be operated on like any other asset.
Note that, unlike assets loaded from a file path, no general mechanism currently exists to deduplicate procedural assets:
calling Assets::add
for every entity that needs the asset will create a new copy of the asset for each entity,
quickly consuming memory.
§Handles and reference counting
Handle
(or their untyped counterpart UntypedHandle
) are used to reference assets in the Assets
collection,
and are the primary way to interact with assets in Bevy.
As a user, you’ll be working with handles a lot!
The most important thing to know about handles is that they are reference counted: when you clone a handle, you’re incrementing a reference count.
When the object holding the handle is dropped (generally because an entity was despawned), the reference count is decremented.
When the reference count hits zero, the asset it references is removed from the Assets
collection.
This reference counting is a simple, largely automatic way to avoid holding onto memory for game objects that are no longer in use. However, it can lead to surprising behavior if you’re not careful!
There are two categories of problems to watch out for:
- never dropping a handle, causing the asset to never be removed from memory
- dropping a handle too early, causing the asset to be removed from memory while it’s still in use
The first problem is less critical for beginners, as for tiny games, you can often get away with simply storing all of the assets in memory at once, and loading them all at the start of the game. As your game grows, you’ll need to be more careful about when you load and unload assets, segmenting them by level or area, and loading them on-demand. This problem generally arises when handles are stored in a persistent “collection” or “manifest” of possible objects (generally in a resource), which is convenient for easy access and zero-latency spawning, but can result in high but stable memory usage.
The second problem is more concerning, and looks like your models or textures suddenly disappearing from the game. Debugging reveals that the entities are still there, but nothing is rendering! This is because the assets were removed from memory while they were still in use. You were probably too aggressive with the use of weak handles (which don’t increment the reference count of the asset): think through the lifecycle of your assets carefully! As soon as an asset is loaded, you must ensure that at least one strong handle is held to it until all matching entities are out of sight of the player.
§Asset dependencies
Some assets depend on other assets to be loaded before they can be loaded themselves. For example, a 3D model might require both textures and meshes to be loaded, or a 2D level might require a tileset to be loaded.
The assets that are required to load another asset are called “dependencies”.
An asset is only considered fully loaded when it and all of its dependencies are loaded.
Asset dependencies can be declared when implementing the Asset
trait by implementing the VisitAssetDependencies
trait,
and the #[dependency]
attribute can be used to automatically derive this implementation.
§Custom asset types
While Bevy comes with implementations for a large number of common game-oriented asset types (often behind off-by-default feature flags!), implementing a custom asset type can be useful when dealing with unusual, game-specific, or proprietary formats.
Defining a new asset type is as simple as implementing the Asset
trait.
This requires TypePath
for metadata about the asset type,
and VisitAssetDependencies
to track asset dependencies.
In simple cases, you can derive Asset
and Reflect
and be done with it: the required supertraits will be implemented for you.
With a new asset type in place, we now need to figure out how to load it.
While AssetReader
describes strategies to read asset bytes from various sources,
AssetLoader
is the trait that actually turns those into your desired in-memory format.
Generally, (only) AssetLoader
needs to be implemented for custom assets, as the AssetReader
implementations are provided by Bevy.
However, AssetLoader
shouldn’t be implemented for your asset type directly: instead, this is implemented for a “loader” type
that can store settings and any additional data required to load your asset, while your asset type is used as the AssetLoader::Asset
associated type.
As the trait documentation explains, this allows various AssetLoader::Settings
to be used to configure the loader.
After the loader is implemented, it needs to be registered with the AssetServer
using App::register_asset_loader
.
Once your asset type is loaded, you can use it in your game like any other asset type!
If you want to save your assets back to disk, you should implement AssetSaver
as well.
This trait mirrors AssetLoader
in structure, and works in tandem with AssetWriter
, which mirrors AssetReader
.
Re-exports§
pub use ron;
Modules§
- The asset prelude.
- Asset processing in Bevy is a framework for automatically transforming artist-authored assets into the format that best suits the needs of your particular game.
Macros§
- Creates a new
embedded
asset by embedding the bytes of the given path into the current binary and registering those bytes with theembedded
AssetSource
. - Returns the
Path
for a givenembedded
asset. This is used internally byembedded_asset
and can be used to get aPath
that matches theAssetPath
used by that asset. - Loads an “internal” asset by embedding the string stored in the given
path_str
and associates it with the given handle. - Loads an “internal” binary asset by embedding the bytes stored in the given
path_str
and associates it with the given handle.
Structs§
- Provides
Handle
andUntypedHandle
for a specific asset type. This should only be used for one specific asset type. - An event emitted when a specific
Asset
fails to load. - Represents a path to an asset in a “virtual filesystem”.
- Provides “asset” loading and processing functionality. An
Asset
is a “runtime value” that is loaded from anAssetSource
, which can be something like a filesystem, a network, etc. - Loads and tracks the state of
Asset
values from a configuredAssetReader
. This can be used to kick off new asset loads and retrieve their current load states. - A mutable iterator over
Assets
. NestedLoader
will create and return asset handles immediately, but only actually load the asset later.NestedLoader
has been configured with info on what type of asset to load at runtime.- A “type erased / boxed” counterpart to
LoadedAsset
. This is used in places where the loaded type is not statically known. NestedLoader
will immediately load an asset when requested.- A context that provides access to assets in
AssetLoader
s, tracks dependencies, and collects asset load state. - An error that occurs when attempting to call
NestedLoader::load
which is configured to work immediately. - The successful result of an
AssetLoader::load
call. This contains the loaded “root” asset and any other “labeled” assets produced by the loader. It also holds the inputAssetMeta
(if it exists) and tracks dependencies: - A “loaded folder” containing handles for all assets stored in a given
AssetPath
. - A “loaded asset” containing the untyped handle for an asset stored in a given
AssetPath
. - An error that occurs when an
AssetLoader
is not registered for a given extension. - An error that occurs when an
AssetLoader
is not registered for a givenstd::any::type_name
. - A builder for loading nested assets inside a
LoadContext
. - Type data for the
TypeRegistry
used to operate on reflectedAsset
s. - Reflect type data struct relating a
Handle<T>
back to theT
asset type. - Defines where the asset will be used.
NestedLoader
will be provided the type of asset as a type parameter onload
.- The internal “strong”
Asset
handle storage forHandle::Strong
andUntypedHandle::Strong
. When this is dropped, theAsset
will be freed. It also stores some asset metadata for easy access from handles. - A system set that holds all “track asset” operations.
NestedLoader
does not know what type of asset it will be loading.- An untyped version of
AssetLoadFailedEvent
.
Enums§
- Events that occur for a specific loaded
Asset
, such as “value changed” events and “dependency” events. - An error that occurs during an
Asset
load. - Configures how / if meta files will be checked. If an asset’s meta file is not checked, the default meta for the asset will be used.
- Controls whether or not assets are pre-processed before being loaded.
- The “asset mode” the server is currently in.
- The load state of an asset’s dependencies.
- An error that occurs while deserializing
AssetMeta
. - A strong or weak handle to a specific
Asset
. If aHandle
isHandle::Strong
, theAsset
will be kept alive until theHandle
is dropped. If aHandle
isHandle::Weak
, it does not necessarily reference a liveAsset
, nor will it keep assets alive. - The load state of an asset.
- An error that occurs when parsing a string type to create an
AssetPath
fails, such as duringAssetPath::parse
. - An error produced when calling
LoadContext::read_asset_bytes
- The recursive load state of an asset’s dependencies.
- Errors preventing the conversion of to/from an
UntypedHandle
and aHandle
. - Errors preventing the conversion of to/from an
UntypedAssetId
and anAssetId
. - An error when attempting to wait asynchronously for an
Asset
to load.
Traits§
- Declares that this type is an asset, which can be loaded and managed by the
AssetServer
and stored inAssets
collections. - Adds asset-related builder methods to
App
. - Loads an
Asset
from a given byteReader
. This can acceptAssetLoader::Settings
, which configure how theAsset
should be loaded. - Extension trait for
AsyncRead
. - Extension trait for
AsyncWrite
. - Provides type-erased access to an
AssetLoader
. - This trait defines how to visit the dependencies of an asset. For example, a 3D model might require both textures and meshes to be loaded.
Functions§
- A system that manages internal
AssetServer
events, such as finalizing asset loads.